package com.nx.platform.es.biz.esspider.handler;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.nx.platform.es.biz.esspider.entity.Item;
import com.nx.platform.es.biz.esspider.resource.DBManager;
import com.nx.platform.es.common.utils.MoreMaps;
import com.nx.platform.es.common.utils.MoreSplitters;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 从数据库中获取单值字段过滤器
 *
 * @author
 * @since May 28, 2016
 */
@HandlerDefine(HandlerType.DB)
public class DBSingleValueFieldsHandler extends AbstractDBHandler {

    private static final Logger LOGGER = LogManager.getLogger(DBSingleValueFieldsHandler.class);
    private static final Gson GSON = new Gson();
    private String sql;
    private String idField;
    private String[] field;
    private boolean toString;
    private boolean factors;

    public DBSingleValueFieldsHandler(String identity, DBManager dbManager) {
        super(identity, dbManager);
    }

    @Override
    public void init(Map<?, ?> settings) throws Exception {
        sql = MoreMaps.getString(settings, "sql");  //sql语句
        idField = MoreMaps.getString(settings, "idField"); // docId
        field = MoreMaps.getStringArray(settings, "field", MoreSplitters.COMMA);  // 有效的数据列
        toString = MoreMaps.getBooleanValue(settings, "toString", false);   //是否将参数变为字符串
        factors = MoreMaps.getBooleanValue(settings, "factors", false);   // 排序因子
        Preconditions.checkNotNull(getJdbcTemplate());
        Preconditions.checkArgument(!Strings.isNullOrEmpty(sql), "sql null or empty");
        Preconditions.checkArgument(!Strings.isNullOrEmpty(idField), "idField null or empty");
    }

    @Override
    public void handle(Map<Long, Item> items) throws Exception {
        if (items.isEmpty()) {
            return;
        }
        // 查询数据库
        SqlParameterSource queryParams;
        if (toString) {
            queryParams = new MapSqlParameterSource(SQL_IDS_KEY,
                    items.keySet().stream().map(String::valueOf).collect(Collectors.toList()));
        } else {
            queryParams = new MapSqlParameterSource(SQL_IDS_KEY, items.keySet());
        }
        List<Map<String, Object>> queryResults = getJdbcTemplate().queryForList(sql, queryParams);
        if (queryResults.isEmpty()) {
            return;
        }
        // 更新文档
        for (Map<String, Object> queryResult : queryResults) {
            Long id = MoreMaps.getLong(queryResult, idField);
            if (id == null) {
                continue;
            }
            Item item = items.get(id);
            if (item == null) {
                continue;
            }
            Stream<String> fieldNames = field != null && field.length > 0 ? Stream.of(field) : queryResult.keySet().stream();
            fieldNames.forEach((String f) -> {
                Object fieldValue = queryResult.get(f);
                item.getDoc().put(f, fieldValue);
                String str = String.valueOf(fieldValue);
                if (factors && str.startsWith("{") && str.endsWith("}")) {
                    try {
                        ScoresFields fields = GSON.fromJson(str, ScoresFields.class);
                        if (fields != null) {
                            item.getDoc().putAll(fields.toMap());
                        }
                    } catch (Throwable e) {
                        LOGGER.warn("read json field failed, id={}, json={}", id, str, e);
                    }
                }
            });
        }
    }

    public static class ScoresFields {

        private Integer pv;
        private Float pv_score;
        private Integer want_buy_count;
        private Float want_buy_count_score;
        private Float want_buy_rate;
        private Integer comment_count;
        private Float comment_count_score;
        private Integer comment_reply_count;
        private Float comment_reply_count_score;

        private Long register_time;
        private Integer level;
        private Integer credit;
        private Integer rain;
        private Float rain_score;
        private Integer buyer_praise_count;
        private Float buyer_praise_rate;
        private Float buyer_praise_rate_score;
        private Float weight_reduce_rate;
        private Float hide_rate;
        private Integer chat_receive_count;
        private Float chat_reply_rate;
        private Long last_login_time;

        public Float getWant_buy_rate() {
            return want_buy_rate;
        }

        public void setWant_buy_rate(Float want_buy_rate) {
            this.want_buy_rate = want_buy_rate;
        }

        public Integer getPv() {
            return pv;
        }

        public void setPv(Integer pv) {
            this.pv = pv;
        }

        public Float getRain_score() {
            return rain_score;
        }

        public void setRain_score(Float rain_score) {
            this.rain_score = rain_score;
        }

        public Integer getBuyer_praise_count() {
            return buyer_praise_count;
        }

        public void setBuyer_praise_count(Integer buyer_praise_count) {
            this.buyer_praise_count = buyer_praise_count;
        }

        public Float getBuyer_praise_rate_score() {
            return buyer_praise_rate_score;
        }

        public void setBuyer_praise_rate_score(Float buyer_praise_rate_score) {
            this.buyer_praise_rate_score = buyer_praise_rate_score;
        }

        public Float getPv_score() {
            return pv_score;
        }

        public void setPv_score(Float pv_score) {
            this.pv_score = pv_score;
        }

        public Integer getWant_buy_count() {
            return want_buy_count;
        }

        public void setWant_buy_count(Integer want_buy_count) {
            this.want_buy_count = want_buy_count;
        }

        public Float getWant_buy_count_score() {
            return want_buy_count_score;
        }

        public void setWant_buy_count_score(Float want_buy_count_score) {
            this.want_buy_count_score = want_buy_count_score;
        }

        public Integer getComment_count() {
            return comment_count;
        }

        public void setComment_count(Integer comment_count) {
            this.comment_count = comment_count;
        }

        public Float getComment_count_score() {
            return comment_count_score;
        }

        public void setComment_count_score(Float comment_count_score) {
            this.comment_count_score = comment_count_score;
        }

        public Integer getComment_reply_count() {
            return comment_reply_count;
        }

        public void setComment_reply_count(Integer comment_reply_count) {
            this.comment_reply_count = comment_reply_count;
        }

        public Float getComment_reply_count_score() {
            return comment_reply_count_score;
        }

        public void setComment_reply_count_score(Float comment_reply_count_score) {
            this.comment_reply_count_score = comment_reply_count_score;
        }

        public Long getRegister_time() {
            return register_time;
        }

        public void setRegister_time(Long register_time) {
            this.register_time = register_time;
        }

        public Integer getLevel() {
            return level;
        }

        public void setLevel(Integer level) {
            this.level = level;
        }

        public Integer getCredit() {
            return credit;
        }

        public void setCredit(Integer credit) {
            this.credit = credit;
        }

        public Integer getRain() {
            return rain;
        }

        public void setRain(Integer rain) {
            this.rain = rain;
        }

        public Float getBuyer_praise_rate() {
            return buyer_praise_rate;
        }

        public void setBuyer_praise_rate(Float buyer_praise_rate) {
            this.buyer_praise_rate = buyer_praise_rate;
        }

        public Float getWeight_reduce_rate() {
            return weight_reduce_rate;
        }

        public void setWeight_reduce_rate(Float weight_reduce_rate) {
            this.weight_reduce_rate = weight_reduce_rate;
        }

        public Float getHide_rate() {
            return hide_rate;
        }

        public void setHide_rate(Float hide_rate) {
            this.hide_rate = hide_rate;
        }

        public Integer getChat_receive_count() {
            return chat_receive_count;
        }

        public void setChat_receive_count(Integer chat_receive_count) {
            this.chat_receive_count = chat_receive_count;
        }

        public Float getChat_reply_rate() {
            return chat_reply_rate;
        }

        public void setChat_reply_rate(Float chat_reply_rate) {
            this.chat_reply_rate = chat_reply_rate;
        }

        public Long getLast_login_time() {
            return last_login_time;
        }

        public void setLast_login_time(Long last_login_time) {
            this.last_login_time = last_login_time;
        }

        /**
         * 非空字段组成Map
         *
         * @return
         */
        public Map<String, Object> toMap() {
            Map<String, Object> target = Maps.newHashMap();
            for (PropertyDescriptor targetPd : BeanUtils.getPropertyDescriptors(this.getClass())) {
                Method readMethod = targetPd.getReadMethod();
                if (readMethod == null) {
                    continue;
                }
                try {
                    if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                        readMethod.setAccessible(true);
                    }
                    Object value = readMethod.invoke(this);
                    if (value == null) {
                        continue;
                    }
                    if ("class".equals(targetPd.getName())) {
                        continue;
                    }
                    target.put(targetPd.getName(), value);
                } catch (Throwable ex) {
                    throw new RuntimeException("Could not read property '" + targetPd.getName() + "'", ex);
                }
            }
            return target;
        }

    }

}
